#include <QTimer>
#include <QDateTime>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QThread>
#include <QFile>
#include <QDataStream>
#include <QDebug>
#include <mutex>
#include <QTextCodec>

#include "serialthread.h"
#include "pcpupdateptl.h"

#include "quiwidget.h"

#pragma execution_character_set("utf-8")

enum
{
    FIRE_STOP = 0,
    FIRE_NEWVER,
    FIRE_DATA,
    FIRE_COMPELTE,
    FIRE_UPDATE,
    FIRE_ABORT
};
#if 0
static uint16_t crc16_ccitt_table[] = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a,
    0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b,
    0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528,
    0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719,
    0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e,
    0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf,
    0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec,
    0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd,
    0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2,
    0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3,
    0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0,
    0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691,
    0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806,
    0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37,
    0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64,
    0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55,
    0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
};
#endif
static const uint16_t crc_table[16] = {
    0x0000, 0x1021, 0x2042, 0x3063,
    0x4084, 0x50a5, 0x60c6, 0x70e7,
    0x8108, 0x9129, 0xa14a, 0xb16b,
    0xc18c, 0xd1ad, 0xe1ce, 0xf1ef
};

uint16_t SerialThread::reentrent_crc_16(uint8_t *data, int32_t len, uint8_t flag)
{
    uint16_t crc16 = 0x0000;
    uint16_t crc_h4, crc_l12;

    if(flag == CCITT_1) crc16 = 0xffff;

    while( len-- ) {
        crc_h4 = (crc16 >> 12);
        crc_l12 = (crc16 << 4);
        crc16 = crc_l12 ^ crc_table[crc_h4 ^ (*data >> 4)];


        crc_h4 = (crc16 >> 12);
        crc_l12 = (crc16 << 4);
        crc16 = crc_l12 ^ crc_table[crc_h4 ^ (*data & 0x0f)];

        data++;
    }

    return crc16;
}

SerialThread::SerialThread(QObject *parent) : QObject(parent),
    m_PortInfo(nullptr)
{
    m_Timer = new QTimer();
    m_Timer->setInterval(1);
    m_Thread = new QThread();
    m_Port = new QSerialPort();

    m_Port->setBaudRate(QSerialPort::Baud115200);
    m_Port->setDataBits(QSerialPort::Data8);
    m_Port->setParity(QSerialPort::NoParity);
    m_Port->setStopBits(QSerialPort::OneStop);
    m_Port->setFlowControl(QSerialPort::NoFlowControl);

    this->moveToThread(m_Thread);
    m_Timer->moveToThread(m_Thread);
    m_Port->moveToThread(m_Thread);

    connect(m_Thread, &QThread::destroyed, m_Thread, &QThread::deleteLater);
    connect(m_Port, &QSerialPort::errorOccurred, this, &SerialThread::SlotPortError);

    connect(m_Timer, SIGNAL(timeout()), this, SLOT(SlotTimer()));
    connect(m_Thread, SIGNAL(started()), m_Timer, SLOT(start()));
    connect(m_Thread, SIGNAL(finished()), m_Timer, SLOT(stop()));

    connect(m_Port, &QSerialPort::readyRead, this, &SerialThread::SlotRead);

    m_Thread->start();

    m_UpdateState = FIRE_STOP;
}

SerialThread::~SerialThread()
{
    if(m_Thread) {
        m_Thread->quit();
        m_Thread->wait();
    }

    disconnect(m_Timer, SIGNAL(timeout()), this, SLOT(SlotTimer()));
    disconnect(m_Port, &QSerialPort::readyRead, this, &SerialThread::SlotRead);

    delete m_Thread;
    delete m_Timer;
    delete m_Port;
}

SerialThread* SerialThread::Instance()
{
    static SerialThread thread;

    return &thread;
}

bool SerialThread::IsOpen()
{
    return m_IsOpen;
}

void SerialThread::SlotSerialOpen(const QString& port, quint32 baud, quint8 parity)
{
    if(m_IsOpen) {
        ClosePort();
    }

    m_PortInfo = new QSerialPortInfo(port);
    m_Port->setPort(*m_PortInfo);
    m_Port->setBaudRate(static_cast<int>(baud));

    if(parity == 0) {
        m_Port->setParity(QSerialPort::NoParity);
    }
    else if(parity == 1) {
        m_Port->setParity(QSerialPort::OddParity);
    }
    else if(parity == 2) {
        m_Port->setParity(QSerialPort::EvenParity);
    }

    m_IsOpen = m_Port->open(QIODevice::ReadWrite);
    if(!m_IsOpen) ClosePort();
}

void SerialThread::SlotSerialClose()
{
    ClosePort();
}

void SerialThread::ClosePort()
{
    if(m_Port->isOpen()) {
        m_Port->close();
    }
    m_IsOpen = false;

    PCPUpdatePtl::Instance()->pcp_update_stop();
    m_UpdateState = FIRE_STOP;

    if(m_PortInfo) {
        delete m_PortInfo;
        m_PortInfo = nullptr;
    }
}

void SerialThread::SlotTimer()
{
    static quint32 cnt = 0;

    if(!m_IsOpen) return;

    if(serialFrameOverTick > 0) {
        serialFrameOverTick--;
        if(!m_IsShell) {
            cnt = (m_UpdateState == FIRE_NEWVER) ? 2000 : 0;
        }
        return;
    }

    if(m_IsShell) {
        if(shellBuf.length() > 0) {
            emit SignalShellRecv(shellBuf);

            shellBuf.clear();
        }
        return;
    }

    if(m_UpdateState == FIRE_NEWVER) {
        if(++cnt >= 2500) {
            cnt = 0;
            PCPUpdatePtl::Instance()->pcp_send_frame(PCP_MSG_QUERY, PCPHEAD_LEN);
        }
    }

    int len = readByteBuf.length();
    if(len == 0) return;

    char *data = readByteBuf.data();
    writeLog("RCV", data, len);

    QString tmp;
    quint16 index;
    uint8_t ret = PCPUpdatePtl::Instance()->pcp_parse_business(readByteBuf, tmp, index);

    if(ret > UPDATE_RET_PACKET) {
        emit SignalUpdateTips(tmp, ret);
    }
    else if(ret == UPDATE_RET_PACKET) {
        m_UpdateState = FIRE_DATA;
        emit SignalUpdateProgress(index);
    }
    else if(ret == UPDATE_RET_START || ret == UPDATE_RET_READY) {
        m_UpdateState = FIRE_DATA;
        writeLog(tmp);
    }
    else if(ret == UPDATE_RET_STOP || ret == UPDATE_RET_REJECT) {
        m_UpdateState = FIRE_STOP;
    }

    readByteBuf.clear();
}

unsigned char SerialThread::HexToChar(unsigned char bChar)
{
    if((bChar>=0x30)&&(bChar<=0x39))
    {
        bChar -= 0x30;
    }
    else if((bChar>=0x41)&&(bChar<=0x46)) // Capital
    {
        bChar -= 0x37;
    }
    else if((bChar>=0x61)&&(bChar<=0x66)) //littlecase
    {
        bChar -= 0x57;
    }
    else
    {
        bChar = 0;
    }
    return bChar;
}

void SerialThread::SlotRead()
{
    QByteArray readByte = m_Port->readAll();

    if(readByte.isEmpty()) return;

    if(m_IsShell) {
        serialFrameOverTick = 100;
        shellBuf += readByte;
        return;
    }

    if(PCPUpdatePtl::Instance()->pcp_is_update()) {
        serialFrameOverTick = 20;
    }
    else {
        serialFrameOverTick = 80;
    }
    readByteBuf += readByte;
}

void SerialThread::SlotPortError(QSerialPort::SerialPortError error)
{
    if(error != QSerialPort::NoError) {
        emit SignalSerialClose();
        ClosePort();
    }
}

void SerialThread::write(const unsigned char* buf, unsigned int len)
{
    if(m_IsOpen) {
        char *sbuf = reinterpret_cast<char*>(const_cast<unsigned char *>(buf));
        m_Port->write(sbuf, len);
        QByteArray tmp = QByteArray(sbuf, static_cast<int>(len));
        QString tmp1 = "SND:" + QUIHelper::byteArrayToHexStr(tmp);
        writeLog(tmp1);
    }
}

void SerialThread::SlotUpdateFireware(const QByteArray& rawBin, quint16 segment, bool is32Check)
{
    if(!m_IsOpen) return;

    QString tmp;
    if(PCPUpdatePtl::Instance()->pcp_new_version(rawBin, tmp, segment, is32Check)) {
        writeLog(tmp);
        //m_Port->write("update\r\n", 8);
        writeLog("SND", "update fireware ready....");
        m_UpdateState = FIRE_NEWVER;
        serialFrameOverTick = 10;

        emit SignalUpdateProgress(1);
    }
    else {
        writeLog("SND", u8"上次升级未完成.");
    }
}

void SerialThread::SlotUpdateStop()
{
    overTick = 0;
    overNum = 0;
    m_UpdateState = FIRE_STOP;

    PCPUpdatePtl::Instance()->pcp_update_stop();
    PCPUpdatePtl::Instance()->pcp_send_frame(PCP_MSG_UPDATE_STOP, PCPHEAD_LEN);
}

void SerialThread::SlotUpdateExecute()
{
    PCPUpdatePtl::Instance()->pcp_send_frame(PCP_MSG_UPDATE_RET, PCPHEAD_LEN);
}

void SerialThread::SlotSerialWriteHex(const QByteArray& data)
{
    if(PCPUpdatePtl::Instance()->pcp_is_update() || !m_IsOpen) return;

    m_Port->write(data);
}

void SerialThread::SlotSerialWriteStr(const QString& sdata)
{
    if(PCPUpdatePtl::Instance()->pcp_is_update() || !m_IsOpen) return;

    m_Port->write(sdata.toStdString().c_str(), sdata.length());
    //writing sended
    //QThread::sleep(1);
}

void SerialThread::writeLog(const char *title, char *buf, int len)
{
    if(len < 4) return;

    QString tmp1;
    bool flag = false;
    bool isChinese = false;

    unsigned char *logbuf = reinterpret_cast<quint8 *>(buf);

    for(int i = 0; i < len - 1; i++)
    {
        if(logbuf[i] == PCPHEAD_1 && logbuf[i+1] == PCPHEAD_2) {
            flag = false;
            break;
        }
        if((logbuf[i] == '\r' && logbuf[i+1] == '\n')
            || (logbuf[i] == 'A' && logbuf[i+1] == 'T')) {
            flag = true;
        }
        if((logbuf[i]&0x80) && (logbuf[i+1]&0x80) && (logbuf[i+2]&0x80) && (logbuf[i+3]&0x80)) {
            isChinese = true;
            flag = true;
            break;
        }
    }

    if(buf[0] == '\r' && buf[1] == '\n') {
        memmove(buf, buf+2, len - 2);
    }

    QByteArray tmp = QByteArray(buf, len);
    if(!flag) { 
        tmp1 = QUIHelper::byteArrayToHexStr(tmp);
    }
    else {
        //tmp1 = tmp1.replace("\r\n", "");
        tmp1 = QUIHelper::getCorrectUnicode(tmp);
        tmp1 = tmp1.trimmed();
    }
    QString tmp2 = QString("%1:%2").arg(title).arg(tmp1);

    emit SignalSerialLog(tmp2);
}

void SerialThread::writeLog(const char *logTitle, const char *tips)
{
    QString tmp2 = QString("%1:%2").arg(logTitle).arg(tips);

    emit SignalSerialLog(tmp2);
}

void SerialThread::writeLog(const QString &ss)
{
    emit SignalSerialLog(ss);
}

void SerialThread::set_shell(bool flag)
{
    if(!m_IsOpen) return;

    std::lock_guard<std::mutex> lock(m_Mutex);
    m_IsShell = flag;
}
